/*
 * @file iso9660_init.c
 *
 *    ISO9660 filesystem initialization.
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <rtems/libio_.h>
#include <rtems/seterr.h>
#include <stdio.h>

#include "iso9660.h"
#include "iso9660_record.h"
#include "iso9660_handlers.h"

const iso9660_mount_options_t ISO9660_default_mount_options = {
    0,  /* UID */
    0,  /* GID */
    ISO9660_MOUNT_MAP_NORMAL, /* Filename mapping */
    "iso-8859-1",  /* character set for I/O operations */

#if HAVE_ICONV
    ISO9660_STANDARD_ANY | ISO9660_EXTENSION_ALL
#else
    /*
     * It's better to disable Joliet when iconv is not available since the iconv_stub
     * won't be able to translate the strings.
     */
    ISO9660_STANDARD_ANY | ISO9660_EXTENSION_SUSP  | ISO9660_EXTENSION_ROCK_RIDGE
#endif
};

/*
 *   Handler table for iso9660 filesystem
 */
const rtems_filesystem_operations_table ISO9660_ops = {
  rtems_iso9660_eval_path,
  rtems_iso9660_evalformake,
  rtems_filesystem_default_link,
  rtems_filesystem_default_unlink,
  rtems_iso9660_node_type,
  rtems_filesystem_default_mknod,
  rtems_filesystem_default_chown,
  rtems_iso9660_freenode,
  rtems_filesystem_default_mount,
  rtems_iso9660_initialize,
  rtems_filesystem_default_unmount,
  rtems_filesystem_default_fsunmount,
  rtems_filesystem_default_utime,
  rtems_filesystem_default_evaluate_link,
  rtems_filesystem_default_symlink,
  rtems_filesystem_default_readlink,
  rtems_filesystem_default_rename,
  rtems_iso9660_statvfs
};


int iso9660_skip_separators(
    const char *str,
    size_t       length,
    int         *index
)
{
  int original_index = *index;

  while (rtems_filesystem_is_separator(str[*index]) && (*index < length))
    (*index)++;

  return *index - original_index;
}

int
rtems_iso9660_eval_path(
  const char                       *path,
  size_t                             pathlen,
  int                                flags,
  rtems_filesystem_location_info_t  *pathloc
)
{
  iso9660_fs_info_t  *fs = rtems_iso9660_get_fsinfo_pathloc(pathloc);
  iso9660_record_t    record, result_record;
  iso9660_record_t   *node_record;
  const char        *node;
  int                 index = 0, node_len, rc, skip;

  iso9660_record_copy(&record, rtems_iso9660_get_record_pathloc(pathloc));
  iso9660_skip_separators(path, pathlen, &index);

  rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EVAL_PATH,
      "Evaluating path `%.*s'\n", pathlen, path);

  while (1)
  {
    /* End of the given path */
    if (path[index] == '\0' || (index >= pathlen))
      break;

    node = path+index;
    node_len = 0;

    while (!rtems_filesystem_is_separator(path[index])
        && (index <= pathlen))
    {
      index++;
      node_len++;
    }

    /* Skipping trailing separators after the current node
     * if skip is > 0 then this is not the end of the path
     */
    skip = iso9660_skip_separators(path, pathlen, &index);

    rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EVAL_PATH,
          "  . Node: `%.*s'\n", node_len, node);

    /* Look for the node in the current directory */
    rc = iso9660_lookup_node_in_dir_record(fs, node, node_len, &record,
        &result_record);

    if (rc < 0)
    {
      iso9660_record_free(&record);
      rtems_iso9660_error_and_return_minus_one(errno,
          "eval_path: lookup node failed.");
    }

    /* We're not at the end of the path and the node is a file,
     * this is not what we want ..
     */
    if ((skip > 0) && !result_record.flags.directory)
    {
      iso9660_record_free(&record);
      iso9660_record_free(&result_record);
      rtems_iso9660_error_and_return_minus_one(ENOENT,
          "eval_path: node is not a directory.");
    }

    /* We're getting out of the ISO volume */
    if ((result_record.location == record.location)
        && (record.location == fs->volume.root_record_location))
    {
      iso9660_record_free(&record);
      iso9660_record_free(&result_record);
      *pathloc = pathloc->mt_entry->mt_point_node;
      return (*pathloc->ops->evalpath_h)(path + index - node_len - skip,
          pathlen - index + node_len + skip, flags, pathloc);
    }

    iso9660_record_free(&record);
    iso9660_record_copy(&record, &result_record);
    iso9660_record_free(&result_record);
  }


  /* Allocate the node record */
  node_record = calloc(1, sizeof(iso9660_record_t));
  if (node_record == NULL)
    rtems_iso9660_error_and_return_minus_one(ENOMEM,
        "eval_path: failed to allocate memory for the resulting record.");

  iso9660_record_copy(node_record, &record);
  rtems_iso9660_set_record_pathloc(pathloc, node_record);

  rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EVAL_PATH,
        "  . Node is a %s\n", node_record->flags.directory?"directory":"file");

  /* Define the correct handler set */
  if (node_record->flags.directory)
    pathloc->handlers = fs->directory_handlers;
  else
    pathloc->handlers = fs->file_handlers;

  pathloc->ops = pathloc->mt_entry->mt_fs_root.ops;
  iso9660_record_free(&record);

  return 0;
}

int
rtems_iso9660_evalformake(
   const char *path,
   rtems_filesystem_location_info_t *pathloc,
   const char **name
)
{
  /*  ISO9660 volumes are read-only, it's not possible to
   * create new nodes on the mounted file-system
   */
  rtems_set_errno_and_return_minus_one(ENOSYS);
}


rtems_filesystem_node_types_t
rtems_iso9660_node_type(
  rtems_filesystem_location_info_t *pathloc
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_pathloc(pathloc);
  rtems_filesystem_node_types_t type;

  if (record->flags.directory)
    type = RTEMS_FILESYSTEM_DIRECTORY;
  else
    type = RTEMS_FILESYSTEM_MEMORY_FILE;

  return type;
}


int
rtems_iso9660_freenode(
 rtems_filesystem_location_info_t      *pathloc
)
{
  iso9660_record_free(rtems_iso9660_get_record_pathloc(pathloc));

  return 0;
}

/*
 *   Called when mounting an ISO9660 device
 *
 * @param mt_entry  Mount table entry
 * @param data      - unused -
 *
 * @return 0 on success, else -1
 */
int
rtems_iso9660_initialize(
  rtems_filesystem_mount_table_entry_t *mt_entry,
  const void                          *data
)
{
  const iso9660_mount_options_t *mount_options;

  if (data == NULL)
    mount_options = &ISO9660_default_mount_options;
  else
    mount_options = (iso9660_mount_options_t*)data;

  return rtems_iso9660_initialize_support(
    mt_entry,
    &ISO9660_ops,
    &ISO9660_file_handlers,
    &ISO9660_directory_handlers,
    mount_options,
    mount_options->version
  );
}


int
rtems_iso9660_statvfs(
 rtems_filesystem_location_info_t  *loc,
 struct statvfs                    *buf
)
{
  iso9660_fs_info_t *fs =  rtems_iso9660_get_fsinfo_pathloc(loc);

  buf->f_bsize = fs->volume.logical_block_size;
  buf->f_frsize = fs->volume.logical_block_size;
  buf->f_blocks = fs->volume.space_size;
  buf->f_bfree = 0;
  buf->f_bavail = 0;
  buf->f_files = 0;
  buf->f_ffree = 0;
  buf->f_favail = 0;
  buf->f_fsid = ISO9660_MAGIC_ID;
  buf->f_namemax = 256;

  return 0;
}
